﻿// This file is part of WifiCC3100 - the Managed Wifi Driver for the TI CC3100
// Hosted on Codeplex: http://cc3100.codeplex.com
// WifiCC3100 is free software licensed under the Apache License 2.0
// © Copyright 2014 ValkyrieTech, LLC
// by Valkyrie-MT

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;
using System.Text;

namespace Networking
{
    public class WifiAdapter : IDisposable
    {
        public event ConnectedEventHandler Connected;
        public delegate void ConnectedEventHandler(WifiAdapter wifiAdapter);

        public event TokenRequestEventHandler TokenRequested;
        public delegate void TokenRequestEventHandler(WifiAdapter wifiAdapter, string tokenName, bool isPost = false, string tokenValue = null, string postAction = null);

        //UdpSocket llmnr = null;
        UdpSocket mdns = null;
        UdpSocket nbns = null;
        UdpSocket llmnr = null;

        private TimeSpan LastNameResponse = TimeSpan.MaxValue;
        private TimeSpan LastNetbiosProbe = TimeSpan.MaxValue;

        private Timer bouncer;
        private Timer socketWatcher;
        
        private TimeSpan bounceFrequency = new TimeSpan(1, 0, 0, 0);  // This is the automated CC3000 reboot frequency.  
        //private TimeSpan bounceFrequency = new TimeSpan(0, 0, 3, 0);  // This is the automated CC3000 reboot frequency.  
        //private TimeSpan bounceFrequency = new TimeSpan(0, 0, 45, 0);  // This is the automated CC3000 reboot frequency.  
        private TimeSpan socketWatchFrequency = new TimeSpan(0, 0, 0, 8);  // This is the automated CC3000 reboot frequency.  
        private TimeSpan HalfHour = new TimeSpan(0, 30, 0);



        TimeSpan lastReset = PowerState.Uptime;

        /// <summary>
        /// Start Networking!    
        /// </summary>
        /// <param name="name">Local Name of this device</param>
        /// <param name="spiBus"></param>
        /// <param name="resetPin"></param>
        /// <param name="interruptPin"></param>
        /// <param name="chipSelectPin"></param>
        public WifiAdapter(string name, string comPort = "COM2", Cpu.Pin hibernatePin = (Cpu.Pin)0x16, Cpu.Pin resetPin = Cpu.Pin.GPIO_NONE, bool ExceptionOnStartFailure = true)
        {
            InitAdapter(name, comPort, hibernatePin, resetPin, ExceptionOnStartFailure);
        }


        private void InitAdapter(string name, string comPort = "COM2", Cpu.Pin hibernatePin = (Cpu.Pin)0x16, Cpu.Pin resetPin = Cpu.Pin.GPIO_NONE, bool ExceptionOnStartFailure = true, ILog log = null)
        {
            if (wifi != null && wifi is CC3100Driver) return;

            wifi = new CC3100Driver(comPort, hibernatePin, resetPin);

            wifi.Log = log;

            this.Name = name;

            wifi.OnConnectedEvent += wifi_OnConnectedEvent;
            wifi.OnTokenRequestEvent += wifi_OnTokenRequestEvent;

            //socketWatcher = new Timer(new TimerCallback(CheckSockets), null, socketWatchFrequency, socketWatchFrequency);
        }

        void wifi_OnTokenRequestEvent(WifiAdapter wifi, string tokenName, bool isPost = false, string tokenValue = null, string postAction = null)
        {
            if (this.TokenRequested != null) this.TokenRequested.Invoke(this, tokenName, isPost, tokenValue, postAction);            
        }

        private void CheckSockets(object state)
        {
            // && this.IsConnectedToWifi
            if (this.IsResetting || wifi == null) return;

            foreach (var aListener in wifi.ListenersBySocket.Values)
            {
                if (aListener is UdpSocket)
                {
                    var udpSocket = aListener as UdpSocket;

                    if (udpSocket.ShouldBeListening && (udpSocket.LastOutgoingMessageSeen.Subtract(udpSocket.LastOutgoingMessageSent).Seconds > 5))
                    {
//                        Debug.Print("RESETTING UDP SOCKET because our own message was not seen on the wire (or in the air)");
//                        Debug.Print("RESETTING WIFI because our own message was not seen on the wire (or in the air)");

                        Trace.Print("Missing UDP packet??");

                        //Log.PrintLine("Missing UDP packet - Wifi Reset");
                        //Trace.Print("Missing UDP packet - Wifi Reset");

                        //lastReset = PowerState.Uptime;
                        //this.Reset();

                        //udpSocket.Reset();  // socket reset does not fix this :(
                    }
                }
            }
        }

        public void ClearWifiProfiles()
        {
            if (wifi != null)
            {
                wifi.DeleteAllProfiles();
            }
        }

        private void BounceNow(object state)
        {
            // && this.IsConnectedToWifi
            //if (!this.IsResetting && PowerState.Uptime.Subtract(lastReset) > bounceFrequency)
            if (!this.IsResetting && PowerState.Uptime.Subtract(lastReset) > bounceFrequency && DateTime.Now.Hour == 4)  // only bounce during the hour of 4 AM
            {
                if (Log != null) Log.PrintLine("Periodic Wifi Reset");
                Trace.Print("Periodic Reset!");

                lastReset = PowerState.Uptime;
                this.Reset();
            }
            else
            {
                Debug.Print("Skipping Periodic Reset because: " + (this.IsResetting ? "Currently Resetting" : ("Last Reset was " + lastReset.ToString())));
            }

        }

        /// <summary>
        /// Start Networking!   
        /// </summary>
        /// <param name="name">This is the name used for local name resolution services.  Setting the local name to null will disable Local Name Resolution</param>
        /// <param name="profile">This is the Hardware interface you are using to connect the CC3000 to the Host controller</param>
        public WifiAdapter(string name = "CC3100", InterfaceProfile profile = InterfaceProfile.CerbuinoBee_Socket2_CC3100, bool ExceptionOnStartFailure = true, ILog log = null)
        {

            // Setup connection from Profile
            switch (profile)
            {
                case InterfaceProfile.CerbuinoBee_Socket2_CC3100:
                    //comPort = "COM2", Cpu.Pin hibernatePin = (Cpu.Pin)0x16, Cpu.Pin resetPin = Cpu.Pin.GPIO_NONE
                    InitAdapter(name, "COM2", (Cpu.Pin)0x16, (Cpu.Pin)0x17, ExceptionOnStartFailure, log);
                    break;
                default:
                    InitAdapter(name, "COM2", (Cpu.Pin)0x16, (Cpu.Pin)0x17, ExceptionOnStartFailure, log);
                    break;
            }
       
        }

        void ConnectPrep()
        {
            wifi.Init(); // power up

            try
            {
                //wifi.UnRegisterMDnsService();
                //wifi.UnRegisterMDnsService(this.Name + DNS.ServiceSuffix);
                wifi.UnRegisterMDnsService(this.Name + "." + DNS.ServiceSuffix);
                wifi.RegisterMDnsService(this.Name + "." + DNS.ServiceSuffix, (Extensions.Join(";", MdnsTextAttributes) + ";" + "mac=" + wifi.MacAddress.ToHexString()).Trim(';'), ServicePort);
                
                // Activate cc3100 hosted mdns service
                wifi.StartService(true, MulticastDnsNamingEnabled);
            }
            catch (NullReferenceException) { }  // wifi was disposed while executing method

            //wifi.SetLocalName(name);

            //bouncer = new Timer(new TimerCallback(BounceNow), null, HalfHour, HalfHour);
        }

        void wifi_OnConnectedEvent(WifiAdapter sender)
        {

            if (wifi.IsConnected == false) return;

            if (Name.IsNullOrEmpty() == false && IsLocalNameServiceStarted == false)
            {
                if (wifi.Name != null && wifi.Name.Trim() != string.Empty)
                {
                    // Listen for MDNS packets
                    //if (mdns == null && MulticastDnsNamingEnabled)  // check for null so we don't open new sockets on a reconnect
                    //{
                    //    mdns = this.OpenUdpSocket();
                    //    mdns.PollFrequency = 400;
                    //    mdns.JoinMulticastGroup(new byte[] { 224, 0, 0, 251 });
                    //    mdns.OnDataReceived += mdns_OnDataReceived;
                    //    mdns.Listen(5353);  // MDNS
                    //}

           

                    // Listen for LLMNR packets
                    if (llmnr == null && WindowsDnsNamingEnabled)  // check for null so we don't open new sockets on a reconnect
                    {
                        llmnr = this.OpenUdpSocket();
                        llmnr.PollFrequency = 200;
                        llmnr.OnDataReceived += llmnr_OnDataReceived;
                        llmnr.JoinMulticastGroup(new byte[] { 224, 0, 0, 252 });
                        llmnr.Listen(5355);  // LLMNR
                    }

                    // Listen for NetBios Name requests
                    if (nbns == null && NetBiosNamingEnabled)  // check for null so we don't open new sockets on a reconnect
                    {
                        nbns = this.OpenUdpSocket();
                        nbns.PollFrequency = 400;
                        nbns.OnDataReceived += nbns_OnDataReceived;
                        nbns.Listen(137);  // Netbios Port
                    }
                }
                
                IsLocalNameServiceStarted = true;
            }
            else
            {
                // This is a reconnection so re-open the sockets
                wifi.ResetListenerSockets();
            }
            
           // SendDeviceAdvertisement();

            // Sleep after first send?  http://e2e.ti.com/support/wireless_connectivity/f/851/p/342177/1196717.aspx
           // Thread.Sleep(1000);  

            if (this.Connected != null && wifi.SuppressReConnectionEvent == false && wifi.IsConnected) this.Connected.Invoke(this);            
        }

        public bool NetBiosNamingEnabled = false;
        public bool WindowsDnsNamingEnabled = true;  //LLMNR
        private bool DhcpServerEnabled = false;  // not exposed

        public bool multicastDnsNamingEnabled = true;  //mDNS

        /// <summary>
        /// Controls whether the network controller responds to local mDNS (Multicast DNS) name requests
        /// </summary>
        public bool MulticastDnsNamingEnabled
        {
            get
            {
                return multicastDnsNamingEnabled;
            }
            set
            {
                if (multicastDnsNamingEnabled != value)
                {
                    if (wifi != null && wifi.IsConnected)
                    {

                        var responseCode = wifi.StartService(webServerEnabled, multicastDnsNamingEnabled, DhcpServerEnabled);
                        if (responseCode != 0) throw new Exception("Error while activating services.  Response code: " + responseCode);

                        multicastDnsNamingEnabled = value;
                    }
                }
            }
        }

        public bool webServerEnabled = true;

        /// <summary>
        /// Controls whether the integrated web server is enabled
        /// </summary>
        public bool WebServerEnabled
        {
            get
            {
                return webServerEnabled;
            }
            set
            {
                if (webServerEnabled != value)
                {
                    if (wifi != null && wifi.IsConnected)
                    {

                        var responseCode = wifi.StartService(webServerEnabled, multicastDnsNamingEnabled, DhcpServerEnabled);
                        if (responseCode != 0) throw new Exception("Error while activating services.  Response code: " + responseCode);

                        webServerEnabled = value;
                    }
                }
            }
        }

        public bool IsDead
        {
            get
            {
                Debugger.Print("Checking for deadlock");

                return wifi != null && wifi.IsDead;
            }
        }

        void nbns_OnDataReceived(UdpSocket sender, byte[] message, byte[] remoteIP, ushort remotePort)
        {
            Debugger.VerbosePrint("NetBIOS Message Detected from IP: " + remoteIP.ToAddress());

            // do not respond to remote mdns requests
            if (Extensions.IsLocal(remoteIP, sender.baseSocket.wifi.IPAddress, sender.baseSocket.wifi.SubnetMask) == false) return;
            
            var nameRequested = message.ParseNameFromNbnsQuery(wifi.Name);

            if (!remoteIP.BytesEqual(this.IPAddress) && remotePort == 137 && (nameRequested != null || message == null))
            {
                byte[] response = NetBiosNaming.ConstructNetbiosResponse(nameRequested, wifi.IPAddress, (message == null ? new byte[2] : message.ParseTransactionID()));
                Debugger.VerbosePrint("Sending NetBIOS Response!  " + response.ToHexString());
                sender.Send(response, this.IPAddress.GetBroadcastAddress(this.SubnetMask), 137);  // broadcast the name
                sender.Send(response, remoteIP, 137);  // Directly address the name to the requestor
                LastNetbiosProbe = PowerState.Uptime;
            }
            else if (remoteIP.BytesEqual(this.IPAddress) && remotePort == 137)
            {
                LastNameResponse = PowerState.Uptime;
            }
        }

        void mdns_OnDataReceived(UdpSocket sender, byte[] message, byte[] remoteIP, ushort remotePort)
        {
            if (this.IPAddress == null || message.Length < 22) return;  // Bad message or disconnected?

            if (remoteIP.BytesEqual(this.IPAddress))
            {
                Debugger.Print("This should never happen.  Message from Self Received.  ");
                // do not respond to our own MDNS messages!  

                return;
            }

            // do not respond to remote mdns requests
            if (Extensions.IsLocal(remoteIP, sender.baseSocket.wifi.IPAddress, sender.baseSocket.wifi.SubnetMask) == false)
            {
                Debugger.Print("Remote mDNS message detected... ignoring.");
                return;
            }

            Debugger.VerbosePrint("Multicast MDNS UDP Message Received (" + message.Length + " bytes) from " + remoteIP.ToAddress() + ":" + remotePort);

            var query = DNS.ParseDnsQuery(message);
            query.OriginIP = new byte[] { 224, 0, 0, 251 }; // MDNS responses are sent back to the Multicast group
            query.OriginPort = 5353;

            this.SendDeviceAdvertisement(sender, query);  

            //var name = message.ParseNameFromDnsQuery(this.Name);

            //if (name != null)
            //{
            //    this.SendDeviceAdvertisement(name);
            //}

        }

        void llmnr_OnDataReceived(UdpSocket sender, byte[] message, byte[] remoteIP, ushort remotePort)
        {
            if (this.IPAddress == null || message.Length < 22) return;  // Bad message or disconnected?

            if (remoteIP.BytesEqual(this.IPAddress))
            {
                Debugger.Print("This should never happen.  Message from Self Received.  ");
                // do not respond to our own MDNS messages!  

                return;
            }

            // do not respond to remote llmnr requests
            if (Extensions.IsLocal(remoteIP, sender.baseSocket.wifi.IPAddress, sender.baseSocket.wifi.SubnetMask) == false)
            {
                Debugger.Print("Remote LLMNR message detected... ignoring.");
                return;
            }

            Debugger.VerbosePrint(DateTime.Now.ToLocalTime() + "." + DateTime.Now.Millisecond + " *** Multicast LLMNR UDP Message Received (" + message.Length + " bytes) from " + remoteIP.ToAddress() + ":" + remotePort);

            var query = DNS.ParseDnsQuery(message);
            query.OriginIP = remoteIP;  // LLMNR responses are sent back to the Requesting device only!
            //query.OriginIP = new byte[] { 192,168,1,255 };  // this is useful for testing by broadcasting the llmnr message, you can inspect it in wireshark from any pc on the network
            query.OriginPort = remotePort;

            if (query.Name == "wpad") return;  // ignore wpad (web proxy automatic discovery)

            this.SendDeviceAdvertisement(sender, query);

            Debugger.VerbosePrint(DateTime.Now.ToLocalTime() + "." + DateTime.Now.Millisecond + " *** Sent Device Advertisement to " + query.OriginIP.ToAddress() + ":" + query.OriginPort + ", trans ID: " + query.TransactionID.ToHexString());

            //var name = message.ParseNameFromDnsQuery(this.Name);

            //if (name != null)
            //{
            //    this.SendDeviceAdvertisement(name);
            //}

        }

        /// <summary>
        /// If the cc3000 is Disconnected from the access point (for whatever reason), should the cc3000 persistently try to reconnect? 
        /// True for yes, False for no. 
        /// Default is True.  
        /// </summary>
        public bool StayConnected
        {
            get { return this.wifi.ShouldReconnect; }
            set { this.wifi.ShouldReconnect = value; }
        }


        /// <summary>
        /// Device name (for Netbios and mDNS); Do not include the .local on the end.  Just letters and numbers.  Also, there is probably a character limit (probably 32), so keep it short.  
        /// </summary>
        public string Name
        {
            get
            {
                return (wifi == null) ? string.Empty : wifi.Name;
            }

            private set
            {
                if (value.IsNullOrWhitespace())
                {
                    wifi.Name = string.Empty;
                }
                else
                {
                    var name = value.ToLower().Trim();
                    wifi.Name = (name.IndexOf(".local") >= 0) ? name.Substring(0, name.Length - 6) : name;  // trim .local
                    wifi.SetLocalName(wifi.Name);  // sets the mDNS name on the cc3100

                    //wifi.Name = (name.IndexOf(".local") >= 0) ? name : name + ".local";  // add .local
                }
                //_encodedName = NetBiosNaming.EncodeNetbiosName(value);
            }
        }


        /// <summary>
        /// The Globally Unique MAC address for this device
        /// </summary>
        public byte[] MacAddress
        {
            get
            {
                return wifi.MacAddress;
            }

            // TODO: implement the nvmem write command in the driver to set the Mac address (Dangerous?)
            //set
            //{
                
            //}

        }

        /// <summary>
        /// IP Address of this device.  To use a static address, just set this value to the desired IP address (also, set your subnet mask, gateway, UseDynamicAddress, and DNS Server)
        /// When using DHCP (the default), once the connection succeeds, this IP Address will become populated with the assigned address.  
        /// Until the address is populated, null is returned.  
        /// </summary>
        public byte[] IPAddress
        {
            get
            {
                return wifi == null ? null : wifi.IPAddress;
            }

            set
            {
                wifi.IPAddress = value;
                //wifi.useDHCP = value == null;
            }
        }

        /// <summary>
        /// Use Dynamic IP Address assignment (also called DHCP).  Set to false to use a Static IP Address.  Default is to use Dynamic Addressing (DHCP).  
        /// </summary>
        public bool UseDynamicAddress
        {
            get
            {
                return wifi.useDHCP;
            }

            set
            {
                wifi.useDHCP = value;
            }
        }

        /// <summary>
        /// Name of the Wifi Network you want to connect to
        /// </summary>
        public string SsidName 
        {
            get
            {
                return wifi == null ? null : wifi.ssid;
            }
        }

        /// <summary>
        /// Wifi Password
        /// </summary>
        public string Password { get; set; }

        /// <summary>
        /// Wifi Security Type
        /// </summary>
        public AccessPoint ConnectedNetwork { get; set; }


        /// <summary>
        /// IP Address of Primary Domain Name Server (DNS).  
        /// Note: this will be overwritten with one provided by the router if DHCP is enabled and router provides a DNS server.
        /// </summary>
        public byte[] DomainNameServer
        {
            get
            {
                return wifi == null ? null : wifi.DnsServer;
            }

            set
            {
                wifi.DnsServer = value;
            }
        }

        ///// <summary>
        ///// Secondary DNS server.  Never automatically assigned.  You have to set this value.  
        ///// It will be used if it is populated and the primary DNS server fails.  
        ///// </summary>
        //public byte[] DomainNameServer2 { get; set; }

        public byte[] Gateway
        {
            get
            {
                return wifi == null ? null : wifi.Gateway;
            }

            set
            {
                wifi.Gateway = value;
            }
        }

        public byte[] SubnetMask
        {
            get
            {
                return wifi == null ? null : wifi.SubnetMask;
            }

            set
            {
                wifi.SubnetMask = value;
            }
        }


        //internal static byte[] GatewayMac { get; set; }


        //public static uint LeaseRenewTime { get; internal set; }

        ///// <summary>
        ///// Interrupt Pin of SPI Bus
        ///// </summary>
        //internal Cpu.Pin IntPin { get; private set; }

        ///// <summary>
        ///// ChipSelect Pin of SPI Bus
        ///// </summary>
        //internal Cpu.Pin CSPin { get; private set; }

        ///// <summary>
        ///// Wifi Enable Pin
        ///// </summary>
        //internal Cpu.Pin EnablePin { get; private set; }

        ///// <summary>
        ///// The SPI Bus port
        ///// </summary>
        //internal SPI.SPI_module SpiPort { get; private set; }

        internal CC3100Driver wifi = null;
        //private string _name;
        //internal static byte[] _encodedName;

        public Socket OpenSocket(SocketType type = SocketType.DGRAM, Protocol protocol = Protocol.UDP, AddressFamily family = AddressFamily.INET)
        {
            var s = new Socket(wifi, (byte)family, (byte)type, (byte)protocol);

            return s;
        }

        public TcpSocket OpenTcpSocket()
        {
            return new TcpSocket(wifi);
        }

        public TcpSocket OpenClientTcpSocket()
        {
            return new TcpSocket(wifi, 10, false);
        }

        public UdpSocket OpenUdpSocket()
        {
            return new UdpSocket(wifi);
        }

        /// <summary>
        /// Connect directly to the desired WiFi AccessPoint right now.  This completely bypasses the system of saved Profiles.  
        /// </summary>
        /// <param name="ap"></param>
        /// <returns></returns>
        public bool DirectConnect(AccessPoint ap, bool stayConnected = true)
        {
            try
            {

                ConnectPrep();

                Debug.Print("Mac Address: " + wifi.GetMacAddress().ToHexString());
                //wifi.SetMacAddress(new byte[] { 8, 0, 40, 34, 105, 50 });

                //wifi.SetWirelessMode();


                //wifi.SetConnectionPolicy(true, false, false, false, true);

                //wifi.DeleteAllProfiles();

                //wifi.Disconnect();

                //// Enable Station mode and DHCP address mode
                //wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                //wifi.SetPowerPolicy();

                return wifi.Connect(ap, stayConnected);
            }
            catch (NullReferenceException) 
            {
                return false;
            } // wifi was disposed while executing method

        }

        /// <summary>
        /// Connect to a Wifi Access Point using the Wifi Protected Setup (WPS) push button on your router
        /// </summary>
        /// <param name="waitForPress">Time to wait (in seconds) for WPS button to be pressed.  (255 is infinite)</param>
        /// <returns></returns>
        public bool WpsConnect(string ssid = null, byte waitForPress = 20, bool stayConnected = true)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            AccessPoint[] accessPoints = null;

            var x = new System.Collections.ArrayList();
            

            if (ssid == null)
            {
                accessPoints = this.NetworkScan(5);
                if (accessPoints.Length == 0) throw new Exception("Failed to find any Access Points");
            }
            else
            {
                accessPoints = new AccessPoint[] { new AccessPoint() { Ssid = ssid } };
            }

            // The waitForPress is all goofed up when there there are multiple access points...  not sure how to address that...
            foreach (var anAccessPoint in accessPoints)
            {
                var start = PowerState.Uptime;

                wifi.Connect(new AccessPoint() { Ssid = anAccessPoint.Ssid, Security = WifiSecurityType.WPS_PBC }, stayConnected, waitForPress);

                while (wifi.IsConnected == false && PowerState.Uptime.Subtract(start) < new TimeSpan(0, 0, waitForPress)) Thread.Sleep(100);

                if (wifi.IsConnected) break;
            }

            return wifi.IsConnected;
        }

        /// <summary>
        /// Connect using Stored Profiles
        /// </summary>
        /// <param name="ap">The Access Point to Connect to.  Warning: only specify the access point when it has changed.  If you call this method with an Access Point every time, you will wear out the flash.  </param>
        /// <returns></returns>
        public void Connect(AccessPoint ap = null)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            wifi.SetScanPolicy(true, 10);

            Debug.Print("Mac Address: " + wifi.GetMacAddress().ToHexString());
            //wifi.SetMacAddress(new byte[] { 8, 0, 40, 34, 105, 50 });
            //Debug.Print("Mac Address2: " + wifi.GetMacAddress().ToHexString());

            if (ap != null)
            {
                wifi.SetConnectionPolicy(false, false, false, false, false);

                wifi.DeleteAllProfiles();
                var x = wifi.AddProfile(ap.Ssid, ap.Security, ap.Key);
                wifi.HasConnectedBefore = false;

                var a = wifi.GetProfile((byte)x);
            }

            wifi.PowerUp();

            wifi.UseCC3100Profiles = true;

            // Connect using Profiles
            //wifi.SetConnectionPolicy(false, false, wifi.UseCC3100Profiles);
            wifi.SetConnectionPolicy();
            wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

            wifi.SetPowerPolicy();
        }

        /// <summary>
        /// Connect using the Stored Profiles.  If none exist, the device goes into Peer-to-Peer mode to allow you to setup a profile.  
        /// </summary>
        /// <param name="ap"></param>
        /// <returns></returns>
        public void AutoConnect(AccessPoint ap = null)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            ConnectPrep();

            //wifi.DeleteAllProfiles();
            //var x2 = wifi.AddProfile(ap, 7);
            //Debug.Print("Add Profile Response: " + x2);

            var currentProfile = wifi.GetProfile(0);

            if (currentProfile == null && ap == null)
            {
                // THERE is no stored profile!  So, go into AP mode to allow adding a profile

                wifi.SetConnectionPolicy(false);
                wifi.Disconnect();

                // Give the Access Point an inviting name
                wifi.AccessPointConfigSet(APConfig.SSID, "PairMe".ToBytes());

                wifi.SetWirelessMode(WirelessMode.AccessPointMode);

                // Enable Station mode and DHCP address mode
                wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });
                
                //wifi.SetConnectionPolicy(true, false, false, false, false); // auto connect to stored profiles (there should be none right now)

                wifi.PowerUp();

                int timeout = 100;

                Debug.Print("Entering AP mode, waiting for profile to be added...");

                

                while (currentProfile == null && timeout-- > 0) 
                {
                    Thread.Sleep(1500);
                    currentProfile = wifi.GetProfile(0);
                }

                if (currentProfile == null)
                {
                    Debug.Print("Failed to connect!");
                    return;
                }
                else
                {
                    Debug.Print("New Profile Added, connecting with it!");
                    

                    // Connect using Profiles
                    wifi.SetConnectionPolicy(true);

                    wifi.SetWirelessMode(WirelessMode.StationMode);
                    
                    Thread.Sleep(1000);
                    
                    // Enable Station mode and DHCP address mode
                    wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                    //wifi.SetWirelessMode();

                    wifi.PowerUp();

                    Thread.Sleep(500);

                    // Enable Station mode and DHCP address mode
                    wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });
                    // Connect using Profiles
                    wifi.SetConnectionPolicy(true);
                }

            }
            else if (currentProfile != null && ap != null && ap.Ssid != currentProfile.Ssid)
            {
                // A different ssid is being specified, update the profile
                Debug.Print("A different profile was passed in... updating.");

                wifi.Disconnect();
                wifi.SetConnectionPolicy(false);

                wifi.DeleteAllProfiles();
                var x = wifi.AddProfile(ap.Ssid, ap.Security, ap.Key);
                wifi.HasConnectedBefore = false;

                wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });
                wifi.SetConnectionPolicy(true);
            }
            else
            {
                Debug.Print("Using current Profile");

                wifi.UseCC3100Profiles = true;

                wifi.SetWirelessMode(WirelessMode.StationMode);

                // Enable Station mode and DHCP address mode
                //wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                //wifi.SetWirelessMode();

                // Connect using Profiles
                wifi.SetConnectionPolicy(true);

                Thread.Sleep(500);

                //wifi.PowerUp();

                //Debug.Print("Powering up... ");

                //wifi.SetWirelessMode(WirelessMode.StationMode);

                // Enable Station mode and DHCP address mode
                //wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                //wifi.SetWirelessMode();

                // Connect using Profiles
                //wifi.SetConnectionPolicy(true, false, false, false, true);
            }


            //wifi.Disconnect();



        }

        public string AccessPointSSID = "PairMe";

        /// <summary>
        /// Connect using the Stored Profiles.  If none exist, the device goes into Peer-to-Peer mode to allow you to setup a profile.  
        /// </summary>
        /// <param name="ap"></param>
        /// <returns></returns>
        private void AutoConnectForThread()
        {
            try
            {
                ConnectPrep();

                //wifi.DeleteAllProfiles();
                //var x2 = wifi.AddProfile(ap, 7);
                //Debug.Print("Add Profile Response: " + x2);

                if (wifi == null || wifi.IsConnected) return;

                var currentProfile = wifi.GetProfile(0);

                if (currentProfile == null)
                {
                    // THERE is no stored profile!  So, go into AP mode to allow adding a profile

                    wifi.SetConnectionPolicy(false);
                    wifi.Disconnect();

                    // Give the Access Point an inviting name
                    wifi.AccessPointConfigSet(APConfig.SSID, AccessPointSSID.ToBytes());

                    wifi.SetWirelessMode(WirelessMode.AccessPointMode);

                    // Enable Station mode and DHCP address mode
                    wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                    //wifi.SetConnectionPolicy(true, false, false, false, false); // auto connect to stored profiles (there should be none right now)

                    wifi.PowerUp();

                    int timeout = 100;

                    Debug.Print("Entering AP mode, waiting for profile to be added...");



                    while (currentProfile == null && timeout-- > 0)
                    {
                        Thread.Sleep(1500);
                        currentProfile = wifi.GetProfile(0);
                    }

                    if (currentProfile == null)
                    {
                        Debug.Print("Failed to connect!");
                        return;
                    }
                    else
                    {
                        Debug.Print("New Profile Added, connecting with it!");


                        // Connect using Profiles
                        wifi.SetConnectionPolicy(true);

                        wifi.SetWirelessMode(WirelessMode.StationMode);

                        Thread.Sleep(1000);

                        // Enable Station mode and DHCP address mode
                        wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                        //wifi.SetWirelessMode();

                        wifi.PowerUp();

                        Thread.Sleep(500);

                        // Enable Station mode and DHCP address mode
                        wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });
                        // Connect using Profiles
                        wifi.SetConnectionPolicy(true);
                    }

                }
                else
                {
                    Debug.Print("Using current Profile");

                    wifi.UseCC3100Profiles = true;

                    wifi.SetWirelessMode(WirelessMode.StationMode);

                    // Enable Station mode and DHCP address mode
                    //wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                    //wifi.SetWirelessMode();

                    // Connect using Profiles
                    wifi.SetConnectionPolicy(true);

                    Thread.Sleep(500);

                    //wifi.PowerUp();

                    //Debug.Print("Powering up... ");

                    //wifi.SetWirelessMode(WirelessMode.StationMode);

                    // Enable Station mode and DHCP address mode
                    //wifi.NetworkConfigSet(NetworkConfig.IPV4_STA_P2P_CL_DHCP_ENABLE, 1, new byte[] { 1 });

                    //wifi.SetWirelessMode();

                    // Connect using Profiles
                    //wifi.SetConnectionPolicy(true, false, false, false, true);
                }
            }
            catch (NullReferenceException) { }  // wifi was disposed while executing method
            
            //wifi.Disconnect();



        }

        Thread ConnectThread = null;

        public void AutoConnectAsync()
        {
            if (ConnectThread != null)
            {
                ConnectThread.Abort();
            }

            ConnectThread = new Thread(AutoConnectForThread);

            ConnectThread.Start();    
        }



        private bool smartConfigActivated = false;

        public bool SmartConfigActivated
        {
            get { return smartConfigActivated; }
            private set { smartConfigActivated = value; }
        }

        public void ActivateSmartConfig(int timeout = 7, bool useEncryption = false, string encryptionKey = "smartconfigAES16")
        {



        }

        /// <summary>
        /// Scan for available Wifi networks and return them as an array.
        /// This is a Synchronous call and will block execution.
        /// Results are returned in order of descending signal strength, so the first AccessPoint has the strongest signal.
        /// </summary>
        public AccessPointResult[] NetworkScan(byte max = 5)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            wifi.Init();

            wifi.SetConnectionPolicy(false, false, false, false, false);

            wifi.Disconnect();

            return wifi.NetworkScan(max);
        }

        //public static void Reset()
        //{
        //    nic.Restart();
        //}

        private TimeSpan LastInternetCheck = TimeSpan.Zero;
        private bool InternetUp = false;
        private bool IsLocalNameServiceStarted = false;
        public ushort ServicePort = 12345;
        public ILog Log
        {
            get
            {
                if (wifi != null) return wifi.Log;
                return null;
            }

            set
            {
                if (wifi != null) wifi.Log = value;
            }

        }
        

            

        /// <summary>
        /// True means you are connected to the Internet.  False means you are not able to reach computers on the Internet, although local computers may be reachable
        /// Note: this call is expensive and synchronous, so don't call it a lot!  Use it Judiciously!  
        /// Also, if you call this more frequently than every 5 seconds, you will get the last cached value if wifi is connected!
        /// </summary>
        public bool ConnectedToInternet
        {
            get
            {
                if (!IsConnectedToWifi) return false;

                if (LastInternetCheck > TimeSpan.Zero && LastInternetCheck > Microsoft.SPOT.Hardware.PowerState.Uptime.Subtract(new TimeSpan(0, 0, 5)))
                {
                    // This prevents hitting the server more than once every 5 seconds.  So someone could put the ConnectedToInternet Property in a loop and it would
                    // not try to hammer the server and potentially get blocked by the server for a DoS attack.  
                    //Trace.Print("Responding with cached value.");

                    return InternetUp;
                }

                LastInternetCheck = Microsoft.SPOT.Hardware.PowerState.Uptime;

                //TODO: implement the http request below
                
                //HttpResponse response = null;

                //try
                //{
                //    var r = new HttpRequest("http://www.msftncsi.com/ncsi.txt");
                //    r.Headers.Add("Accept", "*/*");  // Add custom properties to the Request Header
                //    response = r.Send();
                //}
                //catch { }  // ignore exceptions!  eek!

                //InternetUp = response != null;

                return InternetUp;

                //Trace.Print("Response: " + response.Message);

                // ping something on the internet, or hit msftncsi.com just like Windows PCs do...
                // Great info here: http://blog.superuser.com/2011/05/16/windows-7-network-awareness/
                // Also, since the call can't block, we'll have to hit the server every 10 seconds or so... eek! 
            }
        }

        /// <summary>
        /// True means that this adapter is connected to the Wifi Access Point
        /// </summary>
        public bool IsConnectedToWifi
        {
            get
            {
                try
                {
                    if (wifi == null) return false;
                    return wifi.IsConnected;
                }
                catch
                {
                    return false;
                }
            }
        }


        /// <summary>
        /// Disconnects from the wifi Access Point, but does not power down the wifi adapter.  
        /// </summary>
        public void Stop()
        {
            //if (responderThread == null) return;
            Debugger.Print("Stopping Network");

            try
            {
                //PollingTimer.Change(Timeout.Infinite, Timeout.Infinite);
                //this.IPAddress = null;  // commented out because it would wipe out a static address on disconnect...  bad.  

                wifi.Disconnect();

                //nic.OnLinkChangedEvent = null;
                //nic.OnFrameArrived = null;

                //responderThread.Abort();
            }
            catch { }

            
        }


        /// <summary>
        /// Get host IP by name. Obtain the IP Address of machine on network by its name
        /// </summary>
        /// <param name="p"></param>
        /// <returns>IP Address</returns>
        public byte[] LookupAddress(string p)
        {
            if (wifi == null) return null;

            return wifi.LookupAddress(p);
        }

        public int Ping(object googleIp)
        {
            throw new NotImplementedException();
        }


        public void SendTokenResponseAsync(string tokenName, string value)
        {
            wifi.SendTokenResponseAsync(tokenName, value);
        }


        //public void SetLocalName()
        //{
        //    throw new NotImplementedException();
        //}

        /// <summary>
        /// Populate this string array with whatever information you may want to transmit with the MDNS and LLMNR messages
        /// Typically, this is an array of key=value pairs, something like "vendor=My Company", "dev=MyWidget Pro", "ver=1.0C", "id="abc123"
        /// Individual entries longer than 255 bytes (when UTF-8 encoded to bytes) will be truncated!  
        /// </summary>
        public string[] LocalDnsTextAttributes
        {
            get;
            set;
        }

        /// <summary>
        /// Send the appropriate response to the query
        /// </summary>
        /// <param name="query"></param>
        //public void SendDeviceAdvertisement(UdpSocket socket, DNS.DnsQuery query, string[] textAttributes = null)
        //{
        //    if (query == null) return;

        //    SendDeviceAdvertisement(socket, query.OriginIP, query.OriginPort, query.Name, query.Type, query.TransactionID, textAttributes);
        //}

        public void SendDeviceAdvertisement(UdpSocket socket, DNS.DnsQuery query, string[] textAttributes = null)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            if (this.IPAddress == null) 
            {
                Debugger.Print("Cannot advertise name because IP address is null");
                return;
            }

            if (query.Name == null && (this.Name == null || this.Name == string.Empty))
            {
                Debugger.Print("Wifi Adapter name MUST be set to send local name advertisement. ");
                return;
            }

            if (socket == null)
            {
                Debugger.Print("Socket was null.  Make sure a local name service is running before you send an advertisement");
                return;
            }

            query.Name = query.Name == null ? (this.Name + ".local") : query.Name;
            byte[] nameResponse = null;

            uint ttl = 360;

            if (System.Diagnostics.Debugger.IsAttached) ttl = 36;
            
            if (query.Type == DNS.RecordTypes.A && query.Name.IndexOf(this.Name) >= 0)
                nameResponse = Networking.DNS.ConstructNameResponse(query, this.IPAddress, ttl, textAttributes ?? LocalDnsTextAttributes ?? null);
            else if ((query.Type == DNS.RecordTypes.ANY || query.Type == DNS.RecordTypes.SRV) && query.Name.IndexOf(DNS.ServiceSuffix) >= 0)
                nameResponse = Networking.DNS.ConstructNameResponse(query, this.IPAddress, ttl, textAttributes ?? LocalDnsTextAttributes ?? null);

            //byte[] mdnsResponse = Networking.DNS.ConstructDnsResponse(wifi.Name, new byte[2] { message[0], message[1] }, wifi.IPAddress);
            //byte[] mdnsResponse = Networking.DNS.ConstructMDnsResponse2(name, type, this.IPAddress, 200);

            if (nameResponse != null)
            {
                if (Log != null) Log.PrintLine("Sent " + (query.Name == null ? (this.Name + ".local") : query.Name) + " -> " + this.IPAddress.ToAddress());
                Trace.Print("Sending Local Name Advertisement for " + (query.Name == null ? (this.Name + ".local") : query.Name) + " -> " + this.IPAddress.ToAddress());

                socket.Send(nameResponse, query.OriginIP, query.OriginPort);  
            }
        }

        public void Reset()
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            wifi.ResetAndReconnect();
        }

        public bool IsResetting
        {
            get { return wifi == null ? false : wifi.IsResetting; }
        }

        /// <summary>
        /// Completely disconnects and powers down the wifi chip, then destroys this class
        /// </summary>
        public void Dispose()
        {
            Debugger.VerbosePrint("Disposing of the wifi adapter");

            try
            {
                if (llmnr != null) llmnr.OnDataReceived -= llmnr_OnDataReceived;
                if (mdns != null) mdns.OnDataReceived -= mdns_OnDataReceived;
                if (nbns != null) nbns.OnDataReceived -= nbns_OnDataReceived;
            }
            catch { }

            wifi.ShouldReconnect = false;
            this.Stop();  // disconnect from any connected access points
            wifi.Shutdown();  // turn off the cc3000 chip
            wifi.Dispose();
            //wifi = null;  // dispose of the wifi class ;  update: setting this to null was too problematic because of the threads that may be
        }

        ~WifiAdapter()
        {
	        Debugger.VerbosePrint("Destructor");
        }

        public string[] MdnsTextAttributes 
        {
            get { return wifi.MdnsTextAttributes; }

            set { wifi.MdnsTextAttributes = value; }
        }
    }

    /// <summary>
    /// Profiles for how the Processor/board is connected to the Networking Controller
    /// </summary>
    public enum InterfaceProfile
    {
        /// <summary>
        /// A CC3100 Boost module connected to Socket 2 of a CerbuinoBee
        /// </summary>
        CerbuinoBee_Socket2_CC3100
    };

}